/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#ifndef __dylab_PropertyTree_h__
#define __dylab_PropertyTree_h__

//===========================================================================================
#include <dylab/utils/property/Property.h>
#include <dylab/utils/containers/HashObjContainer.h>
#include <dylab/utils/containers/ListObjContainer.h>
#include <dylab/utils/exceptions/EDoesNotExists.h>
#include <dylab/utils/exceptions/EInvalidFormat.h>

//===========================================================================================
namespace dylab {

    /** This class represents a property tree.
	@remarks
		The tree has nodes, 
		which are properties with the ability to have another property tree as its children.
		There can be multiple nodes with the same name in the same level of the tree.
    */    
    class DYLAB_EXPORT PropertyTree
    {
    public:
        // forward class declaration
        class Node;

    public:
        //
        class DYLAB_EXPORT NodeList
            : public ListObjContainer<Node>
        {
        public:
            NodeList()
                : ListObjContainer<Node>(true)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        // property name -> property object hash container
        class DYLAB_EXPORT NodeListMap
            : public HashObjContainer<String, NodeList>
        {
        public:
            NodeListMap()
                : HashObjContainer<String, NodeList>(true)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        /// This class represents a property, which can have another property tree as its child.
        class DYLAB_EXPORT Node
            : public Property
        {
        private:            
            PropertyTree * mChildren;

        public:
            Node(const String & _name, const String & _value) 
                : Property(_name, _value), mChildren(new PropertyTree())
                    { DYLAB_ASSERT(mChildren != NULL); }

            Node(const Property & _p) 
				: Property(_p), mChildren(new PropertyTree())
                    { DYLAB_ASSERT(mChildren != NULL); }

            virtual ~Node() 
                    { delete mChildren; }

            PropertyTree & getChildren() 
                    { return *mChildren; }
            const PropertyTree & getChildren() const
                    { return *mChildren; }

            Node * findChildNode(const String & _name)
					{ return mChildren->findNode(_name); }

            Node * getChildNode(const String & _name)
                    { return mChildren->getNode(_name); }

            uint32_t getChildNodeCount(const String & _name) const
                    { return mChildren->getNodeCount(_name); }

            NodeList * getChildNodeList(const String & _name)
                    { return mChildren->getNodeList(_name); }

            NodeListMap & getChildNodeListMap()
                    { return mChildren->getNodeListMap(); }
            const NodeListMap & getChildNodeListMap() const
                    { return mChildren->getNodeListMap(); }
            
            String getChildNodeValue(const String & _name, const String & _defaultValue = String::EMPTY) const
                    { return mChildren->getNodeValue(_name, _defaultValue); }

            template <typename _TargetT>
			_TargetT getChildNodeValueT(const String & _name) const
                    { return mChildren->getNodeValueT<_TargetT>(_name); }
            template <typename _TargetT>
			_TargetT getChildNodeValueT(const String & _name, _TargetT _defaultValue) const
                    { return mChildren->getNodeValueT<_TargetT>(_name, _defaultValue); }
        };
        //-----------------------------------------------------------------------------

    private:
        NodeListMap mNodeListMap;

    public:

        PropertyTree();
        PropertyTree(const PropertyTree & _pt);
        virtual ~PropertyTree();

        NodeListMap & getNodeListMap()
                { return mNodeListMap; }
        const NodeListMap & getNodeListMap() const
                { return mNodeListMap; }

        /** Returns signle property with the supplied name.
		@remarks
			If the property does not exists, then throws exception EDoesNotExists.
			If there is more than one property with the same name, then it returns the first one.
		@param[in] _name
			The name of the requested node.
        */
        Node * getNode(const String & _name);

        /** Returns signle property with the supplied name, const version.
		@remarks
			If the property does not exists, then throws exception EDoesNotExists.
			If there is more than one property with the same name, then it returns the first one.
		@param[in] _name
			The name of the requested node.
        */
        const Node * getNode(const String & _name) const;

        /** Returns signle property with the supplied name or NULL.
		@remarks
			If the property does not exists, then returns NULL.
			If there is more than one property with the same name, then it returns the first one.
		@param[in] _name
			The name of the requested node.
        */
        Node * findNode(const String & _name)
                {
                    try { return getNode(_name); }
                    catch (EDoesNotExists &) { return NULL; }
                }

        /** Returns signle property with the supplied name or NULL, const version.
		@remarks
			If the property does not exists, then returns NULL.
			If there is more than one property with the same name, then it returns the first one.
		@param[in] _name
			The name of the requested node.
        */
        const Node * findNode(const String & _name) const
                {
                    try { return getNode(_name); }
                    catch (EDoesNotExists &) { return NULL; }
                }

        /** Returns the list of all properties with the given name.
		@remarks
			If the property does not exists, then throws exception EDoesNotExists.
		@param[in] _name
			The name of the requested properties.
        */
        NodeList * getNodeList(const String & _name);

        /** Returns the list of all properties with the given name, const version.
		@remarks
			If the property does not exists, then throws exception EDoesNotExists.
		@param[in] _name
			The name of the requested properties.
        */
        const NodeList * getNodeList(const String & _name) const;

        /** Returns the number of properties with the specified name.
		@remarks
			if the property does not exists, then returns 0.
		@param[in] _name
			The name of the requested properties.
        */
        uint32_t getNodeCount(const String & _name) const
                {
                    try { return getNodeList(_name)->getCount(); }
                    catch (EDoesNotExists &) { return 0; }
                }

        /** Helper method for directly retrieving node value.
		@remarks
			If the property does not exists, then throws exception EDoesNotExists.
			If there is more than one property with the same name, then it returns the first one.
        */
        String getNodeValue(const String & _name) const
				{ return getNode(_name)->getValue(); }

        /** If the property does not exists, then returns the _defaultValue.
        */
        String getNodeValue(const String & _name, const String & _defaultValue) const
                { 
                    try { return getNodeValue(_name); }
                    catch (EDoesNotExists &) { return _defaultValue; }
                }

        template <typename _TargetT>
        _TargetT getNodeValueT(const String & _name) const
                { return getNodeValue(_name).parse<_TargetT>(); }
        template <typename _TargetT>
        _TargetT getNodeValueT(const String & _name, _TargetT _defaultValue) const
                { return getNodeValue(_name, String::fmt<_TargetT>(_defaultValue)).parse<_TargetT>(_defaultValue); }

		/** Inserts the new property into the tree root.
		@remarks
			If the property with the same name already exists, 
			then it is added to the end of the relevant property list.
		@param[in] _property
			The instance of the Property class to be inserted into this property tree.
		@returns 
			The created property node with the new inserted property.
		*/
		Node * insertProperty(const Property & _property);

		/** Destroys the property with the given name and all of its children.
		@remarks
			The property must exists in the current tree level (no depth-search is performed).
			If the property is multi-valued, then all values are destroyed.
		@param[in] _nodeName
			The name of the node to destroy.
		*/
		void destroyProperty(const String & _nodeName);

		/** Recursively copies the property tree to the specified target tree.		 
		*/
		void copy(PropertyTree & _target) const;

		/** Loads the tree from the file of the given name.		 
		*/
		void load(const String & _fileName);
    };

    //===========================================================================================    
    inline std::ostream & operator << (std::ostream & _os, const PropertyTree & _pt)
            { 
				for (PropertyTree::NodeListMap::ConstIterator nodeList(_pt.getNodeListMap()); nodeList.isValid(); nodeList++)
				{
					for (PropertyTree::NodeList::ConstIterator node(*nodeList()); node.isValid(); node++)
					{
						_os << node->getName() << " = " << node->getValue() << std::endl;
						if (node->getChildren().getNodeListMap().getCount() > 0)
							_os << "{" << node->getChildren() << "}" << std::endl;
					}
				}
				return _os; 
			}
}
//===========================================================================================
#endif // __dylab_PropertyTree_h__
